/*
 * Copyright 2000-2012 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.util.keyFMap;

import com.intellij.openapi.util.Key;
import org.jetbrains.annotations.NotNull;

final class PairElementsFMap implements KeyFMap {
    // invariant: key1.hashCode() < key2.hashCode()
    private final @NotNull
    Key key1;
    private final @NotNull
    Key key2;
    private final @NotNull
    Object value1;
    private final @NotNull
    Object value2;

    PairElementsFMap(@NotNull Key key1, @NotNull Object value1, @NotNull Key key2, @NotNull Object value2) {
        assert key1 != key2;
        // Key hashCodes are unique and ordered
        if (key1.hashCode() < key2.hashCode()) {
            this.key1 = key1;
            this.value1 = value1;
            this.key2 = key2;
            this.value2 = value2;
        } else {
            this.key1 = key2;
            this.value1 = value2;
            this.key2 = key1;
            this.value2 = value1;
        }
    }

    @NotNull
    @Override
    public <V> KeyFMap plus(@NotNull Key<V> key, @NotNull V value) {
        if (key == key1) {
            return value == value1 ? this : new PairElementsFMap(key, value, key2, value2);
        }
        if (key == key2) {
            return value == value2 ? this : new PairElementsFMap(key, value, key1, value1);
        }
        if (key.hashCode() < key1.hashCode()) {
            return new ArrayBackedFMap(new int[]{key.hashCode(), key1.hashCode(), key2.hashCode()}, new Object[]{value, value1, value2});
        } else if (key.hashCode() < key2.hashCode()) {
            return new ArrayBackedFMap(new int[]{key1.hashCode(), key.hashCode(), key2.hashCode()}, new Object[]{value1, value, value2});
        }
        return new ArrayBackedFMap(new int[]{key1.hashCode(), key2.hashCode(), key.hashCode()}, new Object[]{value1, value2, value});
    }

    @NotNull
    @Override
    public KeyFMap minus(@NotNull Key<?> key) {
        if (key == key1) return new OneElementFMap(key2, value2);
        if (key == key2) return new OneElementFMap(key1, value1);
        return this;
    }

    @Override
    public <V> V get(@NotNull Key<V> key) {
        //noinspection unchecked
        return key == key1 ? (V) value1 : key == key2 ? (V) value2 : null;
    }

    @Override
    public int size() {
        return 2;
    }

    @NotNull
    @Override
    public Key[] getKeys() {
        return new Key[]{key1, key2};
    }

    @Override
    public String toString() {
        return "{" + key1 + "=" + value1 + ", " + key2 + "=" + value2 + "}";
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public int getValueIdentityHashCode() {
        int hash = key1.hashCode() * 31 + System.identityHashCode(value1);
        hash = (hash * 31 + key2.hashCode()) * 31 + System.identityHashCode(value2);
        return hash;
    }

    @Override
    public int hashCode() {
        return (key1.hashCode() ^ value1.hashCode()) + (key2.hashCode() ^ value2.hashCode());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof PairElementsFMap)) return false;

        PairElementsFMap map = (PairElementsFMap) o;

        return key1 == map.key1 && value1.equals(map.value1) && key2 == map.key2 && value2.equals(map.value2);
    }

    @Override
    public boolean equalsByReference(KeyFMap o) {
        if (this == o) return true;
        if (!(o instanceof PairElementsFMap)) return false;

        PairElementsFMap map = (PairElementsFMap) o;

        return key1 == map.key1 && value1 == map.value1 && key2 == map.key2 && value2 == map.value2;
    }
}
